How to build a waffle chart with circle-shaped tiles using {waffle} and {ggplot2} libraries in R?

A publication-ready waffle chart to visualize disease outbreaks in the world

data visualization
disease outbreaks
epidemics
pandemics
tidyverse
ggplot2
waffle
Author

Juan Armando Torres Munguía

Published

February 14, 2026

Doi

Overview

Waffle charts are a useful way to visualize part-to-whole relationships. Commonly, waffle charts depict a grid of regular squares to represent the distribution of a categorical variable.

Previously, in a post on this Blog1, I built the following waffle chart to visualize disease outbreaks in the world between 2015 and 2024.

This visualization was developed in support of an analysis paper published in BMJ Global Health, which can be accessed here2. As can be seen, this waffle chart uses the traditional approach of stacking colored squares to represent the number of outbreaks per year by pathogen. In this post, I will illustrate a visual alternative in which circles are used instead of squares to represent the distribution of diseases by year.

About the data

The information is sourced from the global dataset of pandemic- and epidemic-prone disease outbreaks3, whose data are freely available at the GitHub repository of the disease outbreaks project.

This global dataset documents more than 3,450 outbreaks across over 230 countries and territories from January 1996 to January 2026. The diseases are classified according to the International Classification of Diseases, 10th Revision (ICD-10), and the dataset contains information on the year, country, and pathogen for each outbreak.

The dataset of pandemic- and epidemic-prone disease outbreaks is also part of the Humanitarian Data Exchange coordinated by the United Nations Office for the Coordination of Humanitarian Affairs (OCHA).

Set-up

To create the waffle chart, we will use the following R packages:

# Data manipulation
library(tidyverse)

# Waffle chart
library(waffle)

# Visualization extensions for ggplot2
library(paletteer)
library(ggsci)
library(ggtext) 
library(shadowtext)
library(showtext)

# Create styled HTML/LaTeX tables
library(kableExtra)  

Loading data

The table with the organized data can be downloaded here. This table contains the outbreaks recorded by year and by disease worldwide.

outbreaks_year_disease_grouped <- read.csv("outbreaks_year_disease_grouped.csv")

This table is shown below:

outbreaks_year_disease_grouped |>
  arrange(Year, -freq) |>
  kbl(caption = "Disease outbreaks per disease and year") |>
  kable_paper("hover", full_width = F)
Warning in attr(x, "align"): 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
Disease outbreaks per disease and year
Year icd104n freq
2021 COVID-19 220
2021 Poliomyelitis 23
2021 Other 11
2021 Yellow fever 10
2021 Influenza 5
2021 Cholera 2
2021 Monkeypox 2
2021 Dengue 1
2022 COVID-19 230
2022 Monkeypox 53
2022 Other 39
2022 Hepatitis 38
2022 Cholera 29
2022 Yellow fever 12
2022 Poliomyelitis 6
2022 Dengue 5
2022 Influenza 5
2022 Measles 2
2023 COVID-19 210
2023 Dengue 60
2023 Other 40
2023 Cholera 19
2023 Yellow fever 12
2023 Influenza 9
2023 Measles 7
2023 Poliomyelitis 5
2023 Chikungunya 5
2023 Monkeypox 1
2024 COVID-19 142
2024 Dengue 95
2024 Other 32
2024 Yellow fever 13
2024 Influenza 10
2024 Monkeypox 8
2024 Poliomyelitis 1
2025 Influenza 121
2025 COVID-19 109
2025 Chikungunya 38
2025 Monkeypox 35
2025 Cholera 31
2025 Other 27
2025 Measles 7
2025 Yellow fever 5
2025 Poliomyelitis 2

Step 1. Visual elements of the plot

First, I define the font to be used in the final chart. To do this, I commonly use the font_add_google() function from the showtext package. package. This function retrieves font families from the Google Fonts repository. In this example, I use the Atkinson Hyperlegible Next font.

# Add custom font
font_add_google("Atkinson Hyperlegible Next", "Atkinson Hyperlegible Next") 
showtext_auto()

Then, I customize a theme to be applied to the plot.

# Custom theme for the waffle chart
theme_waffle_chart <- function() {

  # Introduce the previously selected font
  theme_minimal(base_family = "Atkinson Hyperlegible Next") +

  # Custom theme settings
  theme(
    # Axis settings
    axis.title = element_blank(), # Remove axis titles
    axis.line = element_blank(), # Remove axis lines
      
    # Title settings
    plot.title.position = "plot", # Position of the title
    plot.title = element_textbox(
      color = "black",
      face = "bold",
      size = 24,
      margin = margin(5, 0, 5, 0), # Top, right, bottom, left margins
      width = unit(1, "npc") # Full plot width
    ),

    plot.margin = unit(c(0.25, 0.25, 0.25, 0.25), "cm"),

    # Legend 
    legend.justification = c(1, 1),
    legend.title = element_text(face = "bold", size = 14),
    legend.title.position = "top",
    legend.text = element_text(face = "bold", size = 12),
    legend.direction = "vertical",
    legend.spacing.x = unit(30, "pt"),
    legend.key.size = unit(11, "pt"),
    legend.key.spacing.y = unit(3, "pt"),
    legend.key.spacing.x = unit(10, "pt"),

    # Subtitle settings
    plot.subtitle = element_textbox(
      color = "grey50",
      face = "bold",
      size = 18,
      margin = margin(0, 0, 40, 0), # Top, right, bottom, left margins
      width = unit(1, "npc")
    ),

    # Caption settings
    plot.caption = element_textbox(
      color = "grey70",
      size = 14,
      hjust = 0
    ),
    plot.caption.position = "plot",

    # Background and margins
    plot.background = element_rect(
      color = "white",
      fill = "white"
    ),
    panel.grid = element_blank(), 
    strip.text.x = element_text(face = "bold", margin = margin(t = 10), color = "black", size = 20),

    # Axis
    axis.ticks.y = element_line(linewidth = 1),
    axis.ticks.length.y = unit(5, "pt"),
    axis.text.x = element_text(face = "bold", color = "black", size = 12),
    axis.text.y = element_text(face = "bold", color = "black", size = 15),
    axis.title.x = element_text(face = "bold", margin = margin(t = 10), color = "black", size = 13),
    axis.title.y = element_text(face = "bold", margin = margin(r = 10), color = "black", size = 17)
    )
}

Then, I define the title, subtitle, and caption of the plot.

# Title, subtitle, and caption for the waffle chart
title_chart <- "Pandemic- and epidemic-prone disease outbreaks in the world | 2021–2025"
subtitle_chart <- "In 2025, influenza emerged as the most reported disease behind outbreaks, being responsible for 121 events."

For the caption, I use rich text by introducing markdown to format specific elements. This is enabled through the ggtext package.

caption_chart <- paste0(
  "**Note:** Each circle represents a country.",
  "<br>", 
  "**Data:** A global dataset of pandemic- and epidemic-prone disease outbreaks (DOI: 10.1038/s41597-022-01797-2).",
  "<br>", 
  "**Graphic:** Juan Torres Munguía."
  )

Step 2. Designing the waffle plot using ggplot2 package

First, I use the geom_waffle() function to construct the waffle chart with squares. The main arguments of this function include size (border size of the tiles), n_rows (number of rows in the waffle grid), flip (orientation of the tiles), color (border color), and make_proportional (whether values are rescaled to proportions).

Additionally, to

ggplot(outbreaks_year_disease_grouped, 
      aes(fill = icd104n, values = freq)) +
  geom_waffle(size = 0.75, 
              n_rows = 10, 
              flip = TRUE,
              color = "white", 
              make_proportional = FALSE) +
  facet_wrap(~Year, 
             nrow = 1, 
             strip.position = "bottom")

In this example, I use the set of colors from the paletteMartin palette of the colorBlindness package. I also add the custom theme() along with the title, subtitle, and caption elements.

ggplot(outbreaks_year_disease_grouped, 
                aes(fill = icd104n, values = freq)) +
  geom_waffle(size = 0.75, 
              n_rows = 10, 
              flip = TRUE,
              color = "white", 
              make_proportional = FALSE) +
  facet_wrap(~Year, 
             nrow = 1, 
             strip.position = "bottom") +
  scale_fill_manual(values = c(paletteer_d("colorBlindness::paletteMartin"))) +
  scale_x_discrete() +
  scale_y_continuous(labels = function(x) x * 10, 
                     expand = c(0, 0)) +
  coord_equal() +
  labs(
    title = title_chart,
    subtitle = subtitle_chart,
    caption = caption_chart,
    x = "", 
    y = "Frequency of disease outbreaks",
    fill = "Pathogen:") +
  guides(fill = guide_legend(position = "right")) +
  theme_waffle_chart()

Step 3. Transforming the squares into circles.

Finally, to transform the tiles from squares into circles, I use radius as an aesthetic inside aes(), assigning the value grid::unit(0.5, “npc”). This produces circular tiles while preserving the waffle layout structure.

ggplot(outbreaks_year_disease_grouped, 
                aes(fill = icd104n, values = freq)) +
  geom_waffle(radius = grid::unit(0.5, "npc"),
              size = 0.75, 
              n_rows = 10, 
              flip = TRUE,
              color = "white", 
              make_proportional = FALSE) +
  facet_wrap(~Year, 
             nrow = 1, 
             strip.position = "bottom") +
  scale_fill_manual(values = c(paletteer_d("colorBlindness::paletteMartin"))) +
  scale_x_discrete() +
  scale_y_continuous(labels = function(x) x * 10, 
                     expand = c(0, 0)) +
  coord_equal() +
  labs(
    title = title_chart,
    subtitle = subtitle_chart,
    caption = caption_chart,
    x = "", 
    y = "Frequency of disease outbreaks",
    fill = "Pathogen:") +
  guides(fill = guide_legend(position = "right")) +
  theme_waffle_chart()

Step 4. Save the waffle chart as a high-quality image

showtext_opts(dpi = 320) # Resolution of 320 dpi for high-quality images ("retina")
ggsave(
  "waffle-pandemics-2026.png",
  dpi = 320,
  width = 14,
  height = 10,
  units = "in"
)
showtext_auto(FALSE)

References

1.
Torres Munguía, J. A. A Waffle Chart of Disease Outbreaks in the World. (2025). doi:10.59350/kg492-0rv24.
2.
Torres Munguía, J. A. & Martínez-Zarzoso, I. Global trends of pandemic-prone and epidemic-prone disease outbreaks in 2024. BMJ global health 11, e020708 (2026).
3.
Torres Munguía, J. A., Badarau, F. C., Díaz Pavez, L. R., Martínez-Zarzoso, I. & Wacker, K. M. A global dataset of pandemic- and epidemic-prone disease outbreaks. Scientific data 9, 683 (2022).

Citation

BibTeX citation:
@online{torres_munguía2026,
  author = {Torres Munguía, Juan Armando},
  title = {How to Build a Waffle Chart with Circle-Shaped Tiles Using
    \{Waffle\} and \{Ggplot2\} Libraries in {R?}},
  date = {2026-02-14},
  url = {https://juan-torresmunguia.netlify.app/blog/posts/waffle-chart-disease-outbreaks-2025/},
  doi = {https://doi.org/10.59350/6ek9t-j8s96},
  langid = {en}
}
For attribution, please cite this work as:
Torres Munguía, J. A. How to build a waffle chart with circle-shaped tiles using {waffle} and {ggplot2} libraries in R? https://juan-torresmunguia.netlify.app/blog/posts/waffle-chart-disease-outbreaks-2025/ (2026) doi:https://doi.org/10.59350/6ek9t-j8s96.